<template>
	<!--游戏交互主体-->
	<div class="game-body">
		<div class="game-container">
			<div v-for="_ in rows" class="game-row">
				<div v-for="_ in cols" class="game-col"></div>
			</div>
		</div>
		<div class="game-tile">
			<tile v-for="t in tiles"
			      :num="t.num"
			      :position="{x: t.x, y: t.y}"/>
		</div>
		<!--游戏结束幕布（展示胜利或失败）-->
		<div v-show="showCurtain" class="game-over-curtain">
			<p class="state-text">{{ stateText }}</p>
			<a class="reset-start"
			   href="javascript:"
			   @click="resetGame">
				再来一次
			</a>
		</div>
	</div>
</template>

<script>
	import Tile from "@/components/game/Tile"

	import {createTile} from "@/components/game/tileElement"
	import Game from "@/game"
	import debounce from "@/components/common/debounce"

	export default {
		name: "GameBody",
		components: {
			Tile
		},
		data() {
			return {
				/*游戏中所有的方块*/
				tiles: [],
				/*记录游戏中可用的位置*/
				xyArr: [],
				/*保存玩家移动之前的方块数据(临时)*/
				beforeMoveTiles: [],
				backStep: false
			};
		},
		methods: {
			/**
			 * 方块位置变化处理
			 */
			positionChange() {
				/**
				 * 如果是回退操作则不往下执行
				 */
				if (this.backStep) {
					this.backStep = false
					return
				}
				if (this.beforeMoveTiles.length)
					this.$store.commit('setBeforeMoveTiles', this.beforeMoveTiles)
				/**
				 * 重合检测，升级数字
				 */
				this.complex()
				/**
				 * 生成新方块
				 */
				this.buildTile()
			},
			/**
			 * 数字方块左移逻辑
			 */
			gameLeft() {
				this.$bus.$emit('beforeMove')
				let len = this.tiles.length
				/**
				 * 按顺序逐行执行moveToLeft
				 */
				for (let i = 1; i <= Game.ROW_SIZE; i++) {
					let tileCells = []
					for (let j = 0; j < len; j++) {
						/**
						 * 每一行有固定的方块，找全之后就停止循环
						 */
						if (tileCells.length === Game.ROW_SIZE) {
							break
						}
						if (this.tiles[j].num === 0) {
							continue
						}
						/**
						 * 同一行的方块添加到数组中
						 * 交给左移算法执行
						 */
						if (this.tiles[j].y === i) {
							tileCells.push(this.tiles[j])
						}
					}
					/**
					 * 整理顺序，最左边的在前面
					 */
					tileCells.sort((a, b) => a.x - b.x)
					this.moveToLeft(tileCells)
				}
			},
			/**
			 * 方块左移算法
			 */
			moveToLeft(tileCells) {
				/**
				 * 合并状态
				 */
				let state = false
				for (let i = 0; i < tileCells.length; i++) {
					let tile = tileCells[i]
					/**
					 * 如果方块不是在最左的位置就一步一步移过去
					 */
					while (tile.x !== 1) {
						/**
						 * 每移动一步前查看下一步的位置是否有方块存在
						 * 如果存在，数字相同就合并，移动结束后再升级，不同就停止移动
						 * 不存在就继续移动
						 */
						if (tile.x - 1 !== 0) {
							let findTile = this.findTileByXy(tile.x - 1, tile.y)
							if (findTile) {
								if (tile.num !== findTile.num || state) {
									/*通过state状态进来的，记得重置状态，不然每次移动同一排只会发生一次合成*/
									if (state) state = false
									break
								} else {
									/*发生合成了置换状态*/
									state = true
									tile.x--
									break
								}
							}
						}
						tile.x--
					}
				}
			},
			/**
			 * 数字方块上移逻辑
			 */
			gameTop() {
				this.$bus.$emit('beforeMove')
				let len = this.tiles.length
				/**
				 * 按顺序逐行执行moveToTop
				 */
				for (let i = 1; i <= Game.COL_SIZE; i++) {
					let tileCells = []
					for (let j = 0; j < len; j++) {
						/**
						 * 每一行有固定的方块，找全之后就停止循环
						 */
						if (tileCells.length === Game.COL_SIZE) {
							break
						}
						if (this.tiles[j].num === 0) {
							continue
						}
						/**
						 * 同一行的方块添加到数组中
						 * 交给上移算法执行
						 */
						if (this.tiles[j].x === i) {
							tileCells.push(this.tiles[j])
						}
					}
					/**
					 * 整理顺序，最上边的在前面
					 */
					tileCells.sort((a, b) => a.y - b.y)
					this.moveToTop(tileCells)
				}
			},
			/**
			 * 方块上移算法
			 */
			moveToTop(tileCells) {
				/**
				 * 合并状态
				 */
				let state = false
				for (let i = 0; i < tileCells.length; i++) {
					let tile = tileCells[i]
					/**
					 * 如果方块不是在最上面的位置就一步一步移过去
					 */
					while (tile.y !== 1) {
						/**
						 * 每移动一步前查看下一步的位置是否有方块存在
						 * 如果存在，数字相同就合并，移动结束后再升级，不同就停止移动
						 * 不存在就继续移动
						 */
						if (tile.y - 1 !== 0) {
							let findTile = this.findTileByXy(tile.x, tile.y - 1)
							if (findTile) {
								if (tile.num !== findTile.num || state) {
									/*通过state状态进来的，记得重置状态，不然每次移动同一排只会发生一次合成*/
									if (state) state = false
									break
								} else {
									/*发生合成了置换状态*/
									state = true
									tile.y--
									break
								}
							}
						}
						tile.y--
					}
				}
			},
			/**
			 * 数字方块右移逻辑
			 */
			gameRight() {
				this.$bus.$emit('beforeMove')
				let len = this.tiles.length
				/**
				 * 按顺序逐行执行moveToRight
				 */
				for (let i = Game.ROW_SIZE; i >= 1; i--) {
					let tileCells = []
					for (let j = 0; j < len; j++) {
						/**
						 * 每一行有固定的方块，找全之后就停止循环
						 */
						if (tileCells.length === Game.ROW_SIZE) {
							break
						}
						if (this.tiles[j].num === 0) {
							continue
						}
						/**
						 * 同一行的方块添加到数组中
						 * 交给右移算法执行
						 */
						if (this.tiles[j].y === i) {
							tileCells.push(this.tiles[j])
						}
					}
					/**
					 * 整理顺序，最右边的在前面
					 */
					tileCells.sort((a, b) => b.x - a.x)
					this.moveToRight(tileCells)
				}
			},
			/**
			 * 方块右移算法
			 */
			moveToRight(tileCells) {
				/**
				 * 合并状态
				 */
				let state = false
				for (let i = 0; i < tileCells.length; i++) {
					let tile = tileCells[i]
					/**
					 * 如果方块不是在最右的位置就一步一步移过去
					 */
					while (tile.x !== Game.ROW_SIZE) {
						/**
						 * 每移动一步前查看下一步的位置是否有方块存在
						 * 如果存在，数字相同就合并，移动结束后再升级，不同就停止移动
						 * 不存在就继续移动
						 */
						if (tile.x + 1 !== Game.ROW_SIZE + 1) {
							let findTile = this.findTileByXy(tile.x + 1, tile.y)
							if (findTile) {
								if (tile.num !== findTile.num || state) {
									/*通过state状态进来的，记得重置状态，不然每次移动同一排只会发生一次合成*/
									if (state) state = false
									break
								} else {
									/*发生合成了置换状态*/
									state = true
									tile.x++
									break
								}
							}
						}
						tile.x++
					}
				}
			},
			/**
			 * 数字方块下移逻辑
			 */
			gameDown() {
				this.$bus.$emit('beforeMove')
				let len = this.tiles.length
				/**
				 * 按顺序逐行执行moveToDown
				 */
				for (let i = Game.COL_SIZE; i >= 1; i--) {
					let tileCells = []
					for (let j = 0; j < len; j++) {
						/**
						 * 每一行有固定的方块，找全之后就停止循环
						 */
						if (tileCells.length === Game.COL_SIZE) {
							break
						}
						if (this.tiles[j].num === 0) {
							continue
						}
						/**
						 * 同一行的方块添加到数组中
						 * 交给下移算法执行
						 */
						if (this.tiles[j].x === i) {
							tileCells.push(this.tiles[j])
						}
					}
					/**
					 * 整理顺序，最下边的在前面
					 */
					tileCells.sort((a, b) => b.y - a.y)
					this.moveToDown(tileCells)
				}
			},
			/**
			 * 方块下移算法
			 */
			moveToDown(tileCells) {
				/**
				 * 合并状态
				 * 防止一排上超过2个的方块合在一起
				 */
				let state = false
				for (let i = 0; i < tileCells.length; i++) {
					let tile = tileCells[i]
					/**
					 * 如果方块不是在最下面的位置就一步一步移过去
					 */
					while (tile.y !== Game.COL_SIZE) {
						/**
						 * 每移动一步前查看下一步的位置是否有方块存在
						 * 如果存在，数字相同就合并，移动结束后再升级，不同就停止移动
						 * 不存在就继续移动
						 */
						if (tile.y + 1 !== Game.COL_SIZE + 1) {
							let findTile = this.findTileByXy(tile.x, tile.y + 1)
							if (findTile) {
								if (tile.num !== findTile.num || state) {
									/*通过state状态进来的，记得重置状态，不然每次移动同一排只会发生一次合成*/
									if (state) state = false
									break
								} else {
									/*发生合成了置换状态*/
									state = true
									tile.y++
									break
								}
							}
						}
						tile.y++
					}
				}
			},
			/**
			 * 根据xy坐标找到方块
			 * 默认返回1，1位置的方块
			 * @param x
			 * @param y
			 * @returns {null || Tile}
			 */
			findTileByXy(x = 1, y = 1) {
				let len = this.tiles.length
				for (let i = 0; i < len; i++) {
					if (this.tiles[i].num === 0) {
						continue
					}
					if (this.tiles[i].x === x && this.tiles[i].y === y) {
						return this.tiles[i]
					}
				}
			},
			/**
			 * 移动完成后检测是否有重合的方块
			 * 把重合的方块进行升级数字，并隐藏一个方块
			 * ?由于删除数组中的元素会触发dom重新渲染，造成不好的显示效果
			 *  所以选择隐藏，而不删除
			 */
			complex() {
				let len = this.tiles.length
				for (let i = 0; i < len; i++) {
					for (let j = i + 1; j < len; j++) {
						let curr = this.tiles[i]
						let next = this.tiles[j]
						if (curr.num === 0 || next.num === 0) {
							continue
						}
						if (curr.x === next.x && curr.y === next.y && curr.num === next.num) {
							curr.num++
							next.num = 0
							this.$store.commit('awarded', Math.pow(2, curr.num))
							this.$bus.$emit('complex')
							break
						}
					}
				}
			},
			/**
			 * 初始化游戏中可用的位置
			 */
			initXyArr() {
				for (let i = 0; i < Game.ROW_SIZE; i++) {
					for (let j = 0; j < Game.COL_SIZE; j++) {
						this.xyArr.push(`_${i + 1}${j + 1}`)
					}
				}
			},
			/**
			 * 刷新可用的方块位置
			 */
			refreshPosition() {
				this.xyArr = []
				this.initXyArr()
				/*遍历删除已被占用的位置*/
				this.tiles.forEach(item => {
					if (item.num === 0) {
						return
					}
					let index = this.xyArr.indexOf(`_${item.x}${item.y}`)
					if (index !== -1) {
						this.xyArr.splice(index, 1)
					}
				})
			},
			/**
			 * 向场上生成一个随机位置的方块
			 */
			buildTile() {
				/**
				 * 没有可用位置时不往下执行
				 */
				if (this.xyArr.length === 0) {
					return
				}
				/**
				 * 从可用位置的数组中随机产生
				 * 确保生成的方块位置不会重复
				 * @type {number}
				 */
				let index = Math.floor(Math.random() * this.xyArr.length)
				let tileElement = this.xyArr[index]
				/**
				 * 根据游戏配置文件设置的概率进行生成
				 * @type {*[]}
				 */
				let prob = []
				for (let i = 0; i < Game.BUILDER_PROB.length; i++) {
					let temp_prob = Game.BUILDER_PROB[i]
					if (temp_prob.limit && this.$store.state.score < temp_prob.limit) {
						continue
					}
					let temp_size = Math.random() * temp_prob.weight
					prob.push({level: temp_prob.level, size: temp_size})
				}
				prob.sort((a, b) => b.size - a.size)
				let level = prob[0].level
				/**
				 * 创建方块
				 * 添加到游戏中
				 */
				let tile = createTile(level, +tileElement[1], +tileElement[2])
				this.tiles.push(tile)
				/**
				 * 刷新可用位置
				 */
				this.refreshPosition()
			},
			/**
			 * 重置游戏
			 */
			resetGame() {
				this.tiles = []
				this.initXyArr()
				this.$store.commit('init')
				this.$store.commit('resetScore')
				this.$store.commit('setBeforeMoveTiles', [])
				setTimeout(() => {
					this.buildTile()
					this.buildTile()
				}, Game.MOVE_ANIM_TIME)
			},
			/**
			 * 判断游戏是否结束
			 */
			isGameOver() {
				let currTile = this.findTileByXy(1, 1)
				/**
				 * 遍历每一个方块，最后一个不遍历
				 */
				while (currTile.x < Game.ROW_SIZE || currTile.y < Game.COL_SIZE) {
					/**
					 * 看它x轴上的下一个方块有没有，数字一样不一样
					 */
					let nextX = this.findTileByXy(currTile.x + 1, currTile.y)
					if (nextX && nextX.num === currTile.num) {
						return false
					}
					/**
					 * 看它y轴上的下一个方块有没有，数字一样不一样
					 */
					let nextY = this.findTileByXy(currTile.x, currTile.y + 1)
					if (nextY && nextY.num === currTile.num) {
						return false
					}
					/**
					 * 当遍历到x轴上最后一个方块时，x从1再次开始遍历，y+1
					 * 否则x+1，y不变
					 */
					if (currTile.x < Game.ROW_SIZE)
						currTile = this.findTileByXy(currTile.x + 1, currTile.y)
					else currTile = this.findTileByXy(1, currTile.y + 1)
				}
				return true
			},
			/**
			 * 保存游戏数据和状态到sessionStorage
			 */
			save() {
				let gameData = JSON.stringify({
					'tiles': this.tiles.filter(item => item.num !== 0),
					'state': {
						'score': this.$store.state.score,
						'winning': this.$store.state.winning
					}
				})
				window.sessionStorage.setItem(Game.STORAGE_KEY, gameData)
			},
			/**
			 * 保存移动之前的数据
			 */
			saveBeforeMove() {
				let tiles = this.tiles.filter(item => item.num !== 0)
				this.beforeMoveTiles = JSON.parse(JSON.stringify(tiles))
			},
			/**
			 * 恢复移动之前的数据
			 */
			recoverBeforeMove() {
				if (this.$store.state.beforeMoveTiles.length && this.$store.state.winning === 0) {
					this.backStep = true
					this.tiles = []
					this.tiles.push(...this.$store.state.beforeMoveTiles)
					this.$store.commit('setBeforeMoveTiles', [])
				}
			}
		},
		computed: {
			rows() {
				return Game.ROW_SIZE
			},
			cols() {
				return Game.COL_SIZE
			},
			showCurtain() {
				return this.$store.state.winning !== 0
			},
			stateText() {
				let state = this.$store.state.winning
				return state === 1 ? '你赢了!' : '你输了!'
			}
		},
		watch: {
			xyArr() {
				/**
				 * 当场上没有可用位置时，可以开始判断是否游戏结束
				 */
				if (this.xyArr.length === 0 && this.isGameOver()) {
					setTimeout(() => this.$bus.$emit('gameOver'), 1000)
				}
			}
		},
		created() {
			let gameData = window.sessionStorage.getItem(Game.STORAGE_KEY)
			/**
			 * 如果保存了游戏数据就恢复数据
			 */
			if (gameData) {
				let data = JSON.parse(gameData)
				this.tiles = []
				this.tiles.push(...data.tiles)
				this.$store.replaceState(Object.assign({}, this.$store.state, data.state))
			} else {
				this.initXyArr()
				this.buildTile()
				this.buildTile()
			}
			/**
			 * 方块位置改变事件防抖处理（非立即执行）
			 */
			let handle = debounce(this.positionChange, Game.MOVE_ANIM_TIME, false)
			this.$bus.$on('positionChange', () => {
				/**
				 * 刷新可用位置
				 */
				this.refreshPosition()
				handle()
			})
		}
	}
</script>

<style scoped>
	@media screen and (max-width: 520px) {
		#app .game-container {
			padding: 10px;
			height: 278px;
		}

		#app .game-row {
			height: 57px;
			margin-bottom: 10px;
		}

		#app .game-row:last-child {
			margin-bottom: 0;
		}

		#app .game-col {
			width: 57px;
			margin-right: 10px;
		}

		#app .game-col:last-child {
			margin-right: 0;
		}

		#app .game-over-curtain {
			padding: 70px 0 0;
		}

		#app .game-over-curtain .state-text {
			font-size: 35px;
		}

		#app .game-over-curtain .reset-start {
			padding: 8px 20px;
		}
	}

	.game-body {
		position: relative;
	}

	.game-container {
		padding: 15px;
		background-color: #183178;
		height: 500px;
		box-sizing: border-box;
	}

	.game-row {
		margin-bottom: 15px;
		height: 106px;
	}

	.game-row:last-child {
		margin-bottom: 0;
	}

	.game-col {
		width: 106px;
		height: 100%;
		margin-right: 15px;
		float: left;
		border-radius: 3px;
		background-color: rgba(238, 228, 218, 0.35);
	}

	.game-col:last-child {
		margin-right: 0;
	}

	.game-tile {
		position: absolute;
		z-index: 2;
		top: 0;
		left: 0;
	}

	.game-over-curtain {
		position: absolute;
		top: 0;
		right: 0;
		bottom: 0;
		left: 0;
		background: rgba(238, 228, 218, 0.5);
		z-index: 100;
		text-align: center;
		padding: 140px 0 0;
	}

	.game-over-curtain .state-text {
		font-size: 60px;
		font-weight: bold;
	}

	.game-over-curtain .reset-start {
		padding: 8px 20px;
		background-color: #183178;
	}
</style>